<template>
  <div ref="editor" class="m-groovy-editor">
    <div
      className="codemirror-tipbox"
      :style="[
        { left: posLeft + 'px' },
        { top: posTop + 'px' },
        { display: tipShow ? 'inline-block' : 'none' },
      ]"
    >
      <ul className="cm-field-ul">
        <li
          v-for="(item, index) in list"
          :key="index"
          @click="handleClick(item)"
          :class="index == 0 ? 'cm-active cm-field-li' : 'cm-field-li'"
        >
          {{item}}
        </li>
      </ul>
    </div>
  </div>
</template>
<script>
import CodeMirror from "codemirror";
import "codemirror/mode/groovy/groovy";
import "codemirror/addon/selection/active-line";
import "codemirror/addon/edit/matchbrackets";
import "codemirror/addon/fold/foldcode";
import "codemirror/addon/fold/foldgutter";
import "codemirror/addon/fold/brace-fold";
import "codemirror/addon/fold/indent-fold";
import "codemirror/addon/fold/comment-fold";
import "codemirror/lib/codemirror.css";
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/theme/neo.css";
import "codemirror/theme/material.css";

export default {
  props: {
    // 源码
    code: {
      type: String,
      default: "",
    },

    // 提示框高度
    toolHeight: {
      type: Number,
      default: 300,
    },

    // 关键词
    keywords: {
      type: Array,
      default() {
        return ["RUN", "SWITCH", "WAIT", "CASE", "THEN", "TIMEOUT"];
      },
    },
  },

  data() {
    return {
      // 当前焦点
      blurFlag: false,
      // 显示提示
      tipShow: false,
      // 下拉位置
      posLeft: 0,
      posTop: 0,
      // 下拉显示的关键词
      list: [],

      // 状态值
      state: {},
    };
  },

  mounted() {
    CodeMirror.defineMode("simplemode", () => {
      return {
        token(stream, state) {
          if (stream.match("aaa")) {
            return "keyword";
          } else if (stream.match("111")) {
            return "number";
          } else {
            stream.next();
            return null;
          }
        },
      };
      // The start state contains the rules that are initially used
      // start: [
      //   // The regex matches the token, the token property contains the type
      //   { regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
      //   // You can match multiple tokens at once. Note that the captured
      //   // groups must span the whole string in this case
      //   {
      //     regex: /(function)(\s+)([a-z$][\w$]*)/,
      //     token: ["keyword", null, "variable-2"],
      //   },
      //   // Rules are matched in the order in which they appear, so there is
      //   // no ambiguity between this one and the one above
      //   {
      //     regex: /(?:function|var|return|if|for|while|else|do|this)\b/,
      //     token: "keyword",
      //   },
      //   { regex: /true|false|null|undefined/, token: "atom" },
      //   {
      //     regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
      //     token: "number",
      //   },
      //   { regex: /\/\/.*/, token: "comment" },
      //   { regex: /\/(?:[^\\]|\\.)*?\//, token: "variable-3" },
      //   // A next property will cause the mode to move to a different state
      //   { regex: /\/\*/, token: "comment", next: "comment" },
      //   { regex: /[-+\/*=<>!]+/, token: "operator" },
      //   // indent and dedent properties guide autoindentation
      //   { regex: /[\{\[\(]/, indent: true },
      //   { regex: /[\}\]\)]/, dedent: true },
      //   { regex: /[a-z$][\w$]*/, token: "variable" },
      //   // You can embed other modes with the mode property. This rule
      //   // causes all code between << and >> to be highlighted with the XML
      //   // mode.
      //   { regex: /<</, token: "meta", mode: { spec: "xml", end: />>/ } },
      // ],
      // // The multi-line comment state.
      // comment: [
      //   { regex: /.*?\*\//, token: "comment", next: "start" },
      //   { regex: /.*/, token: "comment" },
      // ],
      // // The meta property contains global information about the mode. It
      // // can contain properties like lineComment, which are supported by
      // // all modes, and also directives like dontIndentStates, which are
      // // specific to simple modes.
      // meta: {
      //   dontIndentStates: ["comment"],
      //   lineComment: "//",
      // },
    });

    // const editor = CodeMirror.fromTextArea(this.$refs["editor"], {
    const editor = CodeMirror(this.$refs["editor"], {
      // mode: "text/x-groovy",
      mode: "simplemode",
      // theme: theme === "night" ? "material" : "neo",
      theme: "material",
      lineNumbers: true,
      matchBrackets: true,
      styleActiveLine: true,
      foldGutter: false,
      gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
      readOnly: false,
      extraKeys: {
        Tab: function (cm) {
          var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
          cm.replaceSelection(spaces);
        },
      },
    });

    editor.setValue(this.code);
    editor.on("cursorActivity", (cm) => {
      this.cursorActivity(cm);
    });
    // editor.on("changes", (cm) => {
    //   if (this.props.onChange) {
    //     // this.props.onChange(cm.getValue());
    //   }
    // });
    editor.on("focus", (cm) => {
      this.cursorActivity(cm);
      // this.setState({ blurFlag: true });
    });

    editor.addKeyMap({
      // 上下左右控制光标位置
      Up: (cm) => {
        const { tipShow } = this.state;
        if (tipShow) {
          this.enterFuc("up");
        } else {
          cm.execCommand("goLineUp");
        }
      },
      Down: (cm) => {
        const { tipShow } = this.state;
        if (tipShow) {
          this.enterFuc("down");
        } else {
          cm.execCommand("goLineDown");
        }
      },
      Enter: (cm) => {
        const { tipShow } = this.state;
        if (tipShow) {
          this.enterFuc("enter");
        } else {
          cm.execCommand("newlineAndIndent");
        }
      },
    });
    window.addEventListener("click", this.listenner);
  },

  unmounted() {
    window.removeEventListener("click", this.listenner);
  },

  methods: {
    listenner(e) {
      console.log("listenner", this);
      const targetClassName = e.target.className;
      if (typeof targetClassName !== "string") return;
      const list = ["codemirror-tipbox"];
      const returnFalse = list.find((item) => targetClassName.includes(item));
      if (returnFalse) return false;
      const targetPath = e.path;
      let flag = false;
      targetPath.forEach((item) => {
        if (item.className) {
          if (typeof item.className !== "string") return;
          if (
            item.className.includes("CodeMirror-line") ||
            item.className.includes("CodeMirror-linenumber")
          ) {
            flag = true;
          }
        }
      });
      if (flag) {
        this.blurFlag = true;
      } else {
        this.blurFlag = false;
        this.tipShow = false;
      }
      if (targetClassName === "CodeMirror-scroll") {
        this.blurFlag = true;
      }
    },

    cursorActivity(cm) {
      // const { readOnly } = this.props;
      // if (readOnly) return;
      //
      const getCursor = cm.getCursor();
      const pos = cm.cursorCoords(getCursor);
      const getLineInfo = cm.getLine(getCursor.line);
      const cursorBeforeOneChar = getLineInfo.substring(0, getCursor.ch);
      const lastIndex = cursorBeforeOneChar.lastIndexOf(" ", getCursor.ch);
      const content = cursorBeforeOneChar.substring(
        lastIndex + 1,
        getCursor.ch
      );
      const findObj = this.keywords.find((item) =>
        item.toLowerCase().includes(content.toLowerCase())
      );
      if (findObj && content) {
        this.posLeft = pos.left;
        this.posTop = pos.top + 20;
        this.tipShow = true;
        this.search(content);
      } else {
        this.tipShow = false;
      }
    },

    search(val) {
      let arr = [];
      this.keywords.forEach((item) => {
        if (item.toLowerCase().includes(val.toLowerCase())) {
          arr.push(item);
        }
      });
      this.list = arr;
      this.defaultFirst(val, arr);
    },

    defaultFirst(val, list) {
      let findLi = "cm-field-li";
      let active = "cm-active";
      const nodeList = document.querySelectorAll(`.${findLi}`);
      if (nodeList.length > 0) {
        for (let i = 0; i < nodeList.length; i++) {
          nodeList[i].className = findLi;
        }
        nodeList[0].className = `${active} ${findLi}`;
      }
      if (list && list.length === 1 && list[0] === val) {
        this.tipShow = false;
      }
    },

    enterFuc(type) {
      let findLi = "cm-field-li";
      let active = "cm-active";
      const nodeList = document.querySelectorAll(`.${findLi}`);
      const length = nodeList.length;
      let index = 0;
      for (let i = 0; i < length; i++) {
        if (nodeList[i].className.includes(active)) {
          index = i;
        }
      }
      if (type === "up") {
        nodeList[index].className = findLi;
        if (index === 0) {
          nodeList[index].className = `${active} ${findLi}`;
        } else {
          nodeList[index - 1].className = `${active} ${findLi}`;
        }
      } else if (type === "down") {
        nodeList[index].className = findLi;
        if (index === length - 1) {
          nodeList[index].className = `${active} ${findLi}`;
        } else {
          nodeList[index + 1].className = `${active} ${findLi}`;
        }
      } else if (type === "enter") {
        // 点击回车时，模拟鼠标点击，并且隐藏下拉
        const node = document.querySelector(`.${active}`);
        this.handleClick(node.innerText);
        setTimeout(() => {
          this.tipShow = false;
        }, 100);
      }
      document.querySelector(`.${active}`).scrollIntoViewIfNeeded();
    },

    handleClick(item) {
      const getCursor = this.editor.getCursor();
      const getLineInfo = this.editor.getLine(getCursor.line);
      const cursorBeforeOneChar = getLineInfo.substring(0, getCursor.ch);
      const lastIndex = cursorBeforeOneChar.lastIndexOf(" ", getCursor.ch);
      this.editor.setSelection(
        { line: getCursor.line, ch: lastIndex + 1 },
        { line: getCursor.line, ch: getCursor.ch }
      );
      this.editor.replaceSelection(item);
      this.editor.setCursor(getCursor.line, lastIndex + 1 + item.length);
      this.editor.focus();
      this.tipShow = false;
    },
  },
};
</script>
<style lang="less">
.m-groovy-editor {
  position: relative;
  overflow: hidden;
  .codemirror-tipbox {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 999;
    background: #fff;
    width: 200px;
    box-shadow: rgba(119, 119, 119, 0.2) 0px 0px 7px,
      rgba(0, 0, 0, 0) 1px 1px 0px inset, rgba(0, 0, 0, 0) -1px -1px 0px inset;
    font-size: 12px;
    ul {
      margin: 0;
      padding: 0;
      max-height: 226px;
      overflow: auto;
      li {
        list-style: none;
        padding: 5px 10px;
        cursor: pointer;
        &:hover {
          background: #63acff;
          color: #fff;
        }
      }
      .cm-active {
        background: #63acff;
        color: #fff;
      }
    }
  }
  .cm-s-neo .CodeMirror-cursor {
    border-left: 1px solid black;
    border-right: none;
    width: 0;
  }
  .CodeMirror {
    height: 100% !important;
  }
}
</style>